package com.dmbjz.restaurants.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.dmbjz.common.constant.ApiConstant;
import com.dmbjz.common.constant.RedisKeyConstant;
import com.dmbjz.common.exception.ParameterException;
import com.dmbjz.common.model.domain.ResultInfo;
import com.dmbjz.common.model.entity.Restaurant;
import com.dmbjz.common.model.entity.Reviews;
import com.dmbjz.common.model.vo.ReviewsVO;
import com.dmbjz.common.model.vo.ShortDinerInfo;
import com.dmbjz.common.model.vo.SingInDinerInfo;
import com.dmbjz.common.utils.AssertUtil;
import com.dmbjz.restaurants.dao.ReviewsDao;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/*餐厅评论Sercice*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ReviewsService {


    @Autowired
    private ReviewsDao reviewsDao;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private RestaurantService restaurantService;

    @Value("${service.name.ms-oauth-service}")
    private String oauthServiceName;
    @Value("${service.name.ms-diner-service}")
    private String dinersServerName;

    private static final int NINE = 9;

    /**
     * 添加餐厅评论
     *
     * @param restaurantId 餐厅 ID
     * @param accessToken  登录用户 Token
     * @param content      评论内容
     * @param likeIt       是否喜欢
     */
    public void addReview(Integer restaurantId, String accessToken, String content, int likeIt) {

        /*参数校验*/
        AssertUtil.isTrue(restaurantId == null || restaurantId < 1, "请选择要评论的餐厅");
        AssertUtil.isNotEmpty(content, "请输入评论内容");
        AssertUtil.isTrue(content.length() > 800, "评论内容过长，请重新输入");
        /*判断餐厅是否存在*/
        Restaurant restaurant = restaurantService.findById(restaurantId);
        AssertUtil.isTrue(restaurant == null, "该餐厅不存在");
        /*获取登录用户信息*/
        SingInDinerInfo signInDinerInfo = loadSignInDinerInfo(accessToken);
        /*评论插入数据库*/
        Reviews reviews = new Reviews();
        reviews.setContent(content);
        reviews.setFkRestaurantId(restaurantId);
        reviews.setFkDinerId(signInDinerInfo.getId());
        reviews.setLikeIt(likeIt);
        int count = reviewsDao.saveReviews(reviews);
        if (count == 0) {
            return;
        }
        /*使用事务更新餐厅喜欢&不喜欢并添加评论*/
        String num = RedisKeyConstant.restaurants.getKey() + restaurantId;
        String key = RedisKeyConstant.restaurant_new_reviews.getKey() + restaurantId;
        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.multi();                                                 // 标记事务块的开始
                if(likeIt==1){
                    operations.opsForHash().increment(num,"likeVotes",1);
                }else {
                    operations.opsForHash().increment(num, "dislikeVotes",1);
                }
                redisTemplate.opsForList().leftPush(key, reviews);
                return operations.exec();
            }
        });

    }


    /**
     * 获取餐厅最新评论
     *
     * @param restaurantId
     * @param accessToken
     * @return
     */
    public List<ReviewsVO> findNewReviews(Integer restaurantId, String accessToken) {

        AssertUtil.isTrue(restaurantId == null || restaurantId < 1, "请选择要查看的餐厅");    // 参数校验
        String key = RedisKeyConstant.restaurant_new_reviews.getKey() + restaurantId;                    // 获取 Key
        List<Reviews> rangeList = redisTemplate.opsForList().range(key, 0, NINE);                  // 从 Redis 取十条最新评论

        List<ReviewsVO> reviewsVOS = Lists.newArrayList();       // 初始化 VO 集合
        List<Integer> dinerIds = Lists.newArrayList();          // 初始化用户 ID 集合
        /*循环处理评论集合*/
        rangeList.forEach(a -> {
            ReviewsVO temp = new ReviewsVO();
            BeanUtil.copyProperties(a,temp);
            reviewsVOS.add(temp);
            dinerIds.add(a.getFkDinerId());
        });

        /*获取评论用户信息*/
        ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?ids={ids}", ResultInfo.class, StrUtil.join(",", dinerIds));
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        List<LinkedHashMap> dinerInfoMaps = (List<LinkedHashMap>) resultInfo.getData();
        Map<Integer, ShortDinerInfo> dinerInfos = dinerInfoMaps.stream()
                .collect(Collectors.toMap(
                        // key
                        diner -> (int) diner.get("id"),
                        // value
                        diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), true))
                );
        // 完善头像昵称信息
        reviewsVOS.forEach(review -> {
            ShortDinerInfo dinerInfo = dinerInfos.get(review.getFkDinerId());
            if (dinerInfo != null) {
                review.setDinerInfo(dinerInfo);
            }
        });
        // redis list 中只保留最新十条  ltrim
        return reviewsVOS;
    }



    /*获取登录用户信息*/
    private SingInDinerInfo loadSignInDinerInfo(String accessToken) {

        /*判断Token是否为空*/
        AssertUtil.mustLogin(accessToken);
        /*获取登录信息*/
        String url = oauthServiceName+"user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if(resultInfo.getCode()!= ApiConstant.SUCCESS_CODE){
            throw new ParameterException(resultInfo.getMessage());
        }
        SingInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap)resultInfo.getData(),new SingInDinerInfo(),false);
        return dinerInfo;

    }

}
